// <copyright file="SourceCodeGenerator.cs"  company="$registerdorganization$">
// Copyright (c) 2011 Rashans . All rights reserved.
// </copyright>
// <author>Rashan Anushka</author>
// <email>admin@rashans.com</email>
// <date>2012-03-18</date>
// <summary>Helper class for generating source code</summary>

namespace Rashans.WP7LinqToSQLGenerator
{
    using System;
    using System.CodeDom;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Xml;

    /// <summary>
    /// Helper class for generating source code
    /// </summary>
    public static class SourceCodeGenerator
    {
        /// <summary>
        /// Creates the base class code compile unit.
        /// </summary>
        /// <param name="className">Name of the class.</param>
        /// <returns>
        /// the code compile unit for the base entity class
        /// </returns>
        public static CodeTypeDeclaration CreateBaseClassDeclaration(string className)
        {
            // The class definition
            CodeTypeDeclaration classDefinition = new CodeTypeDeclaration(className)
            {
                TypeAttributes = TypeAttributes.Public,
                IsClass = true,
                IsPartial = true
            };

            // Implements INotifyPropertyChanged and INotifyPropertyChanging
            classDefinition.BaseTypes.Add("INotifyPropertyChanged");
            classDefinition.BaseTypes.Add("INotifyPropertyChanging");

            // Add Class Comments
            // Base entity when creating local relational database entities
            classDefinition.Comments.Add(new CodeCommentStatement("<summery>", true));
            classDefinition.Comments.Add(new CodeCommentStatement("Base entity when creating local relational database entities.", true));
            classDefinition.Comments.Add(new CodeCommentStatement("</summery>", true));

            // ------------ INotifyPropertyChanged

            // Declares PropertyChanged event
            CodeMemberEvent eventPropertyChanged = new CodeMemberEvent()
                {
                    Name = "PropertyChanged",
                    Attributes = MemberAttributes.Public,
                    Type = new CodeTypeReference("PropertyChangedEventHandler")
                };

            // Add Comments
            eventPropertyChanged.Comments.Add(new CodeCommentStatement("<summery>", true));
            eventPropertyChanged.Comments.Add(new CodeCommentStatement("Occurs when a property value changes.", true));
            eventPropertyChanged.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Add the event definition
            classDefinition.Members.Add(eventPropertyChanged);

            // Defines a method that returns a string passed to it.
            CodeMemberMethod methodNotifyPropertyChanged = new CodeMemberMethod()
            {
                Attributes = MemberAttributes.FamilyOrAssembly,
                Name = "NotifyPropertyChanged",
                ReturnType = null
            };

            // Add Comments
            methodNotifyPropertyChanged.Comments.Add(new CodeCommentStatement("<summery>", true));
            methodNotifyPropertyChanged.Comments.Add(new CodeCommentStatement("Used to notify that a property has changed.", true));
            methodNotifyPropertyChanged.Comments.Add(new CodeCommentStatement("</summery>", true));
            methodNotifyPropertyChanged.Comments.Add(new CodeCommentStatement("<param name=\"propertyName\">Name of the property.</param>", true));

            // Add the parameter
            methodNotifyPropertyChanged.Parameters.Add(new CodeParameterDeclarationExpression("System.String", "propertyName"));

            // Implement the method body

            // Invokes the delegates for the  PropertyChanged event, passing a local object reference and a new PropertyChangedEventArgs.
            CodeDelegateInvokeExpression invoke1 = new CodeDelegateInvokeExpression(
                new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanged"),
                new CodeExpression[]
                { 
                    new CodeThisReferenceExpression(),
                    new CodeObjectCreateExpression("PropertyChangedEventArgs", new CodeVariableReferenceExpression("propertyName"))
                });

            CodeConditionStatement conditionalStatement = new CodeConditionStatement(
                            new CodeBinaryOperatorExpression(
                                new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanged"),
                                CodeBinaryOperatorType.IdentityInequality,
                                new CodePrimitiveExpression(null)),
                                new CodeExpressionStatement(invoke1));

            methodNotifyPropertyChanged.Statements.Add(conditionalStatement);

            // Add the event definition
            classDefinition.Members.Add(methodNotifyPropertyChanged);

            // ------------ INotifyPropertyChanging

            // Declares PropertyChanging event
            CodeMemberEvent eventPropertyChanging = new CodeMemberEvent()
            {
                Name = "PropertyChanging",
                Attributes = MemberAttributes.Public,
                Type = new CodeTypeReference("PropertyChangingEventHandler")
            };

            // Add Comments
            eventPropertyChanging.Comments.Add(new CodeCommentStatement("<summery>", true));
            eventPropertyChanging.Comments.Add(new CodeCommentStatement("Occurs when a property value is changing.", true));
            eventPropertyChanging.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Add the event definition
            classDefinition.Members.Add(eventPropertyChanging);

            // Defines a method that returns a string passed to it.
            CodeMemberMethod methodNotifyPropertyChanging = new CodeMemberMethod()
            {
                Attributes = MemberAttributes.FamilyOrAssembly,
                Name = "NotifyPropertyChanging",
                ReturnType = null
            };

            // Add Comments
            methodNotifyPropertyChanging.Comments.Add(new CodeCommentStatement("<summery>", true));
            methodNotifyPropertyChanging.Comments.Add(new CodeCommentStatement("Used to notify that a property is about to change.", true));
            methodNotifyPropertyChanging.Comments.Add(new CodeCommentStatement("</summery>", true));
            methodNotifyPropertyChanging.Comments.Add(new CodeCommentStatement("<param name=\"propertyName\">Name of the property.</param>", true));

            // Add the parameter
            methodNotifyPropertyChanging.Parameters.Add(new CodeParameterDeclarationExpression("System.String", "propertyName"));

            // Implement the method body

            // Invokes the delegates for the  PropertyChanged event, passing a local object reference and a new PropertyChangedEventArgs.
            CodeDelegateInvokeExpression invokePropertyChanging = new CodeDelegateInvokeExpression(
                new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanging"),
                new CodeExpression[]
                {
                    new CodeThisReferenceExpression(),
                    new CodeObjectCreateExpression("PropertyChangingEventArgs", new CodeVariableReferenceExpression("propertyName"))
                });

            CodeConditionStatement conditionalPropertyChanging = new CodeConditionStatement(
                            new CodeBinaryOperatorExpression(
                                new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanging"),
                                CodeBinaryOperatorType.IdentityInequality,
                                new CodePrimitiveExpression(null)),
                                new CodeExpressionStatement(invokePropertyChanging));

            methodNotifyPropertyChanging.Statements.Add(conditionalPropertyChanging);

            // Add the event definition
            classDefinition.Members.Add(methodNotifyPropertyChanging);

            return classDefinition;
        }

#if UseTypedClasses

        /// <summary>
        /// Creates the data context code compile unit.
        /// </summary>
        /// <param name="database">The database.</param>
        /// <returns>
        /// the code compile unit for the data context
        /// </returns>
        public static CodeTypeDeclaration CreateDataContextDeclaration(Database database)
        {
            if (database == null)
            {
                throw new ArgumentNullException("database");
            }

            // The class definition
            var className = database.Class == null ? database.Name : database.Class;
            CodeTypeDeclaration classDefinition = new CodeTypeDeclaration(className)
            {
                TypeAttributes = TypeAttributes.Public,
                IsClass = true,
                IsPartial = true
            };

            // Inherit from DataContext
            classDefinition.BaseTypes.Add("DataContext");

            // ----------  Class Attributes

            // [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "This is how we define LINQ tables.")]
            CodeAttributeDeclaration codeAttrDecl = new CodeAttributeDeclaration(
                "System.Diagnostics.CodeAnalysis.SuppressMessage",
                new CodeAttributeArgument(new CodePrimitiveExpression("SA1401:FieldsMustBePrivate")),
                new CodeAttributeArgument(new CodePrimitiveExpression("Microsoft.StyleCop.CSharp.MaintainabilityRules")),
                new CodeAttributeArgument("Justification", new CodePrimitiveExpression("This is how we define LINQ tables.")));

            classDefinition.CustomAttributes.Add(codeAttrDecl);

            // ---------- Constructor

            // Build the constructor
            var constructor = new CodeConstructor();
            constructor.Attributes = MemberAttributes.Public;

            // Declares a parameter of type string named "connectionString".
            constructor.Parameters.Add(new CodeParameterDeclarationExpression("System.String", "connectionString"));

            // Calls a base class constructor with the TestStringParameter parameter.
            constructor.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("connectionString"));

            // Add the constructor
            classDefinition.Members.Add(constructor);

            // ---------- Add Table Definitions
            var tableName = string.Empty;
            foreach (Table table in database.Table)
            {
                if (table != null)
                {
                    // public Table<LocationItem> Locations;
                    tableName = table.Member == null ? table.Name : table.Member;
                    if (!string.IsNullOrEmpty(tableName) &&
                        !tableName.Contains("__VERSION"))
                    {
                        classDefinition.Members.Add(new CodeMemberField("Table`1[" + table.Type.Name + "]", tableName)
                        {
                            Attributes = MemberAttributes.Public
                        });
                    }
                }
            }

            return classDefinition;
        }

        /// <summary>
        /// Creates the entity code compile units.
        /// </summary>
        /// <param name="database">The database.</param>
        /// <returns>list of entity declarations</returns>
        public static IList<CodeTypeDeclaration> CreateEntityDeclarations(Database database)
        {
            if (database == null)
            {
                throw new ArgumentNullException("database");
            }

            IList<CodeTypeDeclaration> entityDefinitions = new List<CodeTypeDeclaration>();
            var postProcessList = new Dictionary<Association, CodeTypeDeclaration>();
            CodeTypeDeclaration entityDefinition;
            foreach (Table table in database.Table)
            {
                if (table != null &&
                    table.Type != null &&
                    !table.Type.Name.Contains("__VERSION"))
                {
                    entityDefinition = CreateClassForType(table.Type, ref postProcessList);
                    if (entityDefinition != null)
                    {
                        entityDefinitions.Add(entityDefinition);
                    }
                }
            }

            // Process the associations to add attach/detach methods
            foreach (var entry in postProcessList)
            {
                if (entry.Key != null &&
                    entry.Value != null)
                {
                    var sourceAssociation = entry.Key;
                    var typeDeclaration = entry.Value;
                    var endAssociation = postProcessList
                        .Where(item => item.Key != null && item.Key != entry.Key && item.Key.Name == entry.Key.Name)
                        .FirstOrDefault();

                    if (endAssociation.Key != null)
                    {
                        var attchement = GetAttachmentDefinition(sourceAssociation, endAssociation.Key, true);
                        var detchement = GetAttachmentDefinition(sourceAssociation, endAssociation.Key, false);

                        // Add the methods
                        typeDeclaration.Members.Add(attchement);
                        typeDeclaration.Members.Add(detchement);
                    }
                }
            }

            return entityDefinitions;
        }

        /// <summary>
        /// Adds the column property.
        /// </summary>
        /// <param name="column">The column.</param>
        /// <param name="theField">The field.</param>
        /// <param name="theProperty">The property.</param>
        /// <returns>true if the transformation success; else false</returns>
        private static bool GetColumnDefinitions(Column column, out CodeMemberField theField, out CodeMemberProperty theProperty)
        {
            // TODO: Handle errors (e.g. missing values)
            if (column == null || column.Type == null)
            {
                theField = null;
                theProperty = null;
                return false;
            }

            string type = column.Type;
            string propName;

            // Check for a version column
            if (type.Contains("Binary") && column.IsVersionSpecified && column.IsVersion)
            {
                // We will add it seperately
                theField = null;
                theProperty = null;
                return false;
            }

            if (!string.IsNullOrEmpty(column.Member))
            {
                propName = column.Member;
            }
            else if (!string.IsNullOrEmpty(column.Name))
            {
                propName = column.Name;
            }
            else
            {
                theField = null;
                theProperty = null;
                return false;
            }

            string fieldName = "_" + propName;

            // Define the private field
            theField = new CodeMemberField(type, fieldName)
            {
                Attributes = MemberAttributes.Private
            };

            // Adding a doc comment
            theField.Comments.Add(new CodeCommentStatement("<summery>", true));
            theField.Comments.Add(new CodeCommentStatement(string.Format("The storage field for {0}", propName), true));
            theField.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Define the property
            theProperty = new CodeMemberProperty
            {
                Attributes = column.AccessModifier == AccessModifier.Internal ? MemberAttributes.Family : MemberAttributes.Public,
                Name = propName,
                HasGet = true,

                Type = new CodeTypeReference(type)
            };

            // Add an attribute ([Column])
            theProperty.CustomAttributes.Add(GetColumnAttribute(column, fieldName));

            // Adding a doc comment
            theProperty.Comments.Add(new CodeCommentStatement("<summery>", true));
            theProperty.Comments.Add(new CodeCommentStatement(string.Format("Gets or sets the {0}", propName), true));
            theProperty.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Define the getter
            theProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));

            // Define the setter

            // Notify the  PropertyChanging event, passing property name
            CodeMethodInvokeExpression propChanging = new CodeMethodInvokeExpression(
                new CodeBaseReferenceExpression(),                                          // targetObject that contains the method to invoke.
                "NotifyPropertyChanging",                                                   // methodName indicates the method to invoke.
                new CodeExpression[] 
                {
                    new CodeVariableReferenceExpression(string.Format("\"{0}\"", propName))     // parameters array contains the parameters for the method.
                });

            // Notify the  PropertyChanged event, passing property name
            CodeMethodInvokeExpression propChanged = new CodeMethodInvokeExpression(
                new CodeBaseReferenceExpression(),                                          // targetObject that contains the method to invoke.
                "NotifyPropertyChanged",                                                    // methodName indicates the method to invoke.
                new CodeExpression[] 
                {
                    new CodeVariableReferenceExpression(string.Format("\"{0}\"", propName))
                });

            CodeConditionStatement conditionalStatement = new CodeConditionStatement(
                            new CodeBinaryOperatorExpression(
                                new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                                CodeBinaryOperatorType.IdentityInequality,
                                new CodePropertySetValueReferenceExpression()),
                                new CodeStatement[] // True statements
                                {
                                    new CodeExpressionStatement(propChanging),
                                    new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()),
                                    new CodeExpressionStatement(propChanged)
                                });

            theProperty.SetStatements.Add(conditionalStatement);

            return true;
        }

        /// <summary>
        /// Gets the association definitions.
        /// </summary>
        /// <param name="association">The association.</param>
        /// <param name="theField">The field.</param>
        /// <param name="theProperty">The property.</param>
        /// <param name="constructor">The constructor.</param>
        /// <param name="attachmentsNeeded">if set to <c>true</c> [attachments needed].</param>
        /// <returns>
        /// true if the transformation success; else false
        /// </returns>
        private static bool GetAssociationDefinitions(Association association, out CodeMemberField theField, out CodeMemberProperty theProperty, ref CodeConstructor constructor, ref bool attachmentsNeeded)
        {
            // TODO: Handle errors (e.g. missing values)
            if (association == null)
            {
                theField = null;
                theProperty = null;
                return false;
            }

            string type = association.Type;
            string propName;
            bool isOneToOne = false;

            if (!string.IsNullOrEmpty(association.Member))
            {
                propName = association.Member;
            }
            else if (!string.IsNullOrEmpty(association.Name))
            {
                propName = association.Name;
            }
            else
            {
                theField = null;
                theProperty = null;
                return false;
            }

            string fieldName = string.IsNullOrEmpty(association.Storage) ? "_" + propName : association.Storage;

            // Define the private field
            if ((association.CardinalitySpecified && association.Cardinality == Cardinality.One) ||
                (association.IsForeignKeySpecified && association.IsForeignKey))
            {
                isOneToOne = true;

                theField = new CodeMemberField("EntityRef`1[" + type + "]", fieldName)
                {
                    Attributes = MemberAttributes.Private
                };

                // Define the property
                theProperty = new CodeMemberProperty
                {
                    Attributes = association.AccessModifier == AccessModifier.Internal ? MemberAttributes.Family : MemberAttributes.Public,
                    Name = propName,
                    HasGet = true,
                    Type = new CodeTypeReference(type)
                };
            }
            else
            {
                theField = new CodeMemberField("EntitySet`1[" + type + "]", fieldName)
                {
                    Attributes = MemberAttributes.Private
                };

                // Define the property
                theProperty = new CodeMemberProperty
                {
                    Attributes = association.AccessModifier == AccessModifier.Internal ? MemberAttributes.Family : MemberAttributes.Public,
                    Name = propName,
                    HasGet = true,
                    Type = new CodeTypeReference("EntitySet`1[" + type + "]")
                };
            }

            // Adding a doc comment to the field
            theField.Comments.Add(new CodeCommentStatement("<summery>", true));
            theField.Comments.Add(new CodeCommentStatement(string.Format("The storage field for {0}", propName), true));
            theField.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Add an attribute ([Column])
            theProperty.CustomAttributes.Add(GetColumnAttribute(association, fieldName));

            // Adding a doc comment to the property (Association)
            theProperty.Comments.Add(new CodeCommentStatement("<summery>", true));
            theProperty.Comments.Add(new CodeCommentStatement(string.Format("Gets or sets the {0}", propName), true));
            theProperty.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Define the property getter and setter
            if (isOneToOne)
            {
                // Define the getter
                theProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName + ".Entity")));

                // Define the setter
                // Notify the  PropertyChanging event, passing property name
                CodeMethodInvokeExpression propChanging = new CodeMethodInvokeExpression(
                    new CodeBaseReferenceExpression(),                                          // targetObject that contains the method to invoke.
                    "NotifyPropertyChanging",                                                   // methodName indicates the method to invoke.
                    new CodeExpression[]
                    {
                        new CodeVariableReferenceExpression(string.Format("\"{0}\"", propName)) // parameters array contains the parameters for the method.
                    });

                // Notify the  PropertyChanged event, passing property name
                CodeMethodInvokeExpression propChanged = new CodeMethodInvokeExpression(
                    new CodeBaseReferenceExpression(),                                          // targetObject that contains the method to invoke.
                    "NotifyPropertyChanged",                                                    // methodName indicates the method to invoke.
                    new CodeExpression[]
                    {
                        new CodeVariableReferenceExpression(string.Format("\"{0}\"", propName)) // parameters array contains the parameters for the method.
                    });

                CodeConditionStatement conditionalStatement = new CodeConditionStatement(
                            new CodeBinaryOperatorExpression(
                                new CodePropertySetValueReferenceExpression(),
                                CodeBinaryOperatorType.IdentityInequality,
                                new CodePrimitiveExpression(null)),
                                new CodeStatement[]
                                {
                                    new CodeAssignStatement(
                                        new CodeVariableReferenceExpression(association.ThisKey),
                                        new CodePropertyReferenceExpression(new CodePropertySetValueReferenceExpression(), association.OtherKey))
                                });

                // Define the setter
                theProperty.SetStatements.Add(propChanging);
                theProperty.SetStatements.Add(new CodeAssignStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName + ".Entity"),
                    new CodePropertySetValueReferenceExpression()));

                theProperty.SetStatements.Add(conditionalStatement);
                theProperty.SetStatements.Add(propChanged);

                // Initialize the storage field on construtor
                // e.g this._ReportCard = default(EntityRef<ReportCard>);
                constructor.Statements.Add(new CodeAssignStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                    new CodeDefaultValueExpression(new CodeTypeReference("EntityRef`1[" + type + "]"))));
            }
            else
            {
                // Define the getter
                theProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));

                // Define the setter
                // this._ClassRooms.Assign(value);
                theProperty.SetStatements.Add(new CodeMethodInvokeExpression(
                    new CodeThisReferenceExpression(),
                    fieldName + ".Assign",
                    new CodeExpression[] { new CodePropertySetValueReferenceExpression() }));

                // Add attch/detach action to the construtor
                // E.g. : this._ClassRooms = new EntitySet<ClassRoom>(new Action<ClassRoom>(this.attach_ClassRooms), new Action<ClassRoom>(this.detach_ClassRooms));
                constructor.Statements.Add(new CodeAssignStatement(
                        new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), // Left
                        new CodeObjectCreateExpression(
                            new CodeTypeReference("EntitySet`1[" + type + "]"),
                            new CodeObjectCreateExpression(
                                new CodeTypeReference("Action`1[" + type + "]"),
                                new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "Attach" + fieldName)),
                                new CodeObjectCreateExpression(
                                    new CodeTypeReference("Action`1[" + type + "]"),
                                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "Detach" + fieldName)))));

                // Signal the need to add attachment callbacks
                attachmentsNeeded = true;
            }

            return true;
        }

        /// <summary>
        /// Gets the attachment definition.
        /// </summary>
        /// <param name="association">The association.</param>
        /// <param name="endAssociation">The end association.</param>
        /// <param name="isAttachMethod">if set to <c>true</c> [is attach method].</param>
        /// <returns>method declaration</returns>
        private static CodeMemberMethod GetAttachmentDefinition(Association association, Association endAssociation, bool isAttachMethod)
        {
            var methodPrefix = isAttachMethod ? "Attach" : "Detach";
            string fieldName = "_" + association.Member;

            // Defines a method that returns a string passed to it.
            CodeMemberMethod method = new CodeMemberMethod()
            {
                Attributes = MemberAttributes.Private,
                Name = methodPrefix + fieldName,
                ReturnType = null
            };

            // Add Comments
            var comment = isAttachMethod ? "Called during an add operation" : "Called during a remove operation";
            method.Comments.Add(new CodeCommentStatement("<summery>", true));
            method.Comments.Add(new CodeCommentStatement(comment, true));
            method.Comments.Add(new CodeCommentStatement("</summery>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"entity\">the entity.</param>", true));

            // Add the parameter
            method.Parameters.Add(new CodeParameterDeclarationExpression(association.Type, "entity"));

            // Implement the method body

            // Notify the  PropertyChanging event, passing property type
            method.Statements.Add(
                new CodeMethodInvokeExpression(
                    new CodeBaseReferenceExpression(),
                    "NotifyPropertyChanging",
                    new CodeExpression[]
                    {
                        new CodeVariableReferenceExpression(string.Format("\"{0}\"", association.Type))
                    }));

            // Set the property value
            if (isAttachMethod)
            {
                method.Statements.Add(new CodeAssignStatement(
                   new CodeVariableReferenceExpression("entity." + endAssociation.Member),
                   new CodeThisReferenceExpression()));
            }
            else
            {
                method.Statements.Add(new CodeAssignStatement(
                   new CodeVariableReferenceExpression("entity." + endAssociation.Member),
                    new CodePrimitiveExpression(null)));
            }

            return method;
        }

        /// <summary>
        /// Creates the entity class for a given node definition.
        /// </summary>
        /// <param name="tableType">Type of the table.</param>
        /// <param name="postProcessList">The post process list.</param>
        /// <returns>
        /// code type declaration of a class
        /// </returns>
        private static CodeTypeDeclaration CreateClassForType(Type tableType, ref Dictionary<Association, CodeTypeDeclaration> postProcessList)
        {
            if (tableType == null)
            {
                throw new ArgumentNullException("tableType");
            }

            // The class definition
            CodeTypeDeclaration classDefinition = new CodeTypeDeclaration(tableType.Name)
            {
                TypeAttributes = TypeAttributes.Public,
                IsClass = true,
                IsPartial = true
            };

            // Inherit from our custom LocalEntityBase (which is also generated!)
            classDefinition.BaseTypes.Add("LocalEntityBase");

            // Add Class Attributes
            // [Table]
            CodeAttributeDeclaration codeAttrDecl = new CodeAttributeDeclaration("Table");
            classDefinition.CustomAttributes.Add(codeAttrDecl);

            // Add a default constructor
            CodeConstructor constructor = new CodeConstructor()
            {
                Attributes = MemberAttributes.Public
            };

            classDefinition.Members.Add(constructor);

            // Define the properties and associations
            foreach (var item in tableType.Items)
            {
                CodeMemberField theField;
                CodeMemberProperty theProperty;

                if (item is Column)
                {
                    var column = item as Column;

                    if (GetColumnDefinitions(column, out theField, out theProperty))
                    {
                        // Add the definition to the class
                        classDefinition.Members.Add(theField);
                        classDefinition.Members.Add(theProperty);
                    }
                }
                else if (item is Association)
                {
                    var association = item as Association;
                    bool attachmentsNeeded = false;
                    if (GetAssociationDefinitions(association, out theField, out theProperty, ref constructor, ref attachmentsNeeded))
                    {
                        // Add the definition to the class
                        classDefinition.Members.Add(theField);
                        classDefinition.Members.Add(theProperty);

                        // Post Process the association
                        if (attachmentsNeeded)
                        {
                            postProcessList.Add(association, classDefinition);
                        }
                        else
                        {
                            postProcessList.Add(association, null);
                        }
                    }
                }
            }

            // Add a version column (http://msdn.microsoft.com/en-us/library/hh286406%28v=VS.92%29.aspx)
            var versionField = new CodeMemberField("Binary", "_version")
           {
               Attributes = MemberAttributes.Private
           };

            versionField.Comments.Add(new CodeCommentStatement("<summery>", true));
            versionField.Comments.Add(new CodeCommentStatement("Version column aids update performance.", true));
            versionField.Comments.Add(new CodeCommentStatement("</summery>", true));
            versionField.CustomAttributes.Add(new CodeAttributeDeclaration("Column", new CodeAttributeArgument("IsVersion", new CodePrimitiveExpression(true))));
            classDefinition.Members.Insert(0, versionField);

            return classDefinition;
        }

        /// <summary>
        /// Gets the column attribute.
        /// </summary>
        /// <param name="column">The column.</param>
        /// <param name="fieldName">Name of the storage field.</param>
        /// <returns>the column attribute</returns>
        private static CodeAttributeDeclaration GetColumnAttribute(Column column, string fieldName)
        {
            var attributeList = new List<CodeAttributeArgument>();

            if (column.IsPrimaryKeySpecified)
            {
                attributeList.Add(new CodeAttributeArgument("IsPrimaryKey", new CodePrimitiveExpression(column.IsPrimaryKey)));
            }

            if (!string.IsNullOrEmpty(fieldName))
            {
                attributeList.Add(new CodeAttributeArgument("Storage", new CodePrimitiveExpression(fieldName)));
            }

            if (column.AutoSyncSpecified && column.AutoSync != AutoSync.Default)
            {
                var syncValue = string.Format("AutoSync.{0}", column.AutoSync.ToString());

                attributeList.Add(new CodeAttributeArgument("AutoSync", new CodeVariableReferenceExpression(syncValue)));
            }

            if (!string.IsNullOrEmpty(column.DbType))
            {
                attributeList.Add(new CodeAttributeArgument("DbType", new CodePrimitiveExpression(column.DbType)));
            }

            if (column.IsDbGeneratedSpecified)
            {
                attributeList.Add(new CodeAttributeArgument("IsDbGenerated", new CodePrimitiveExpression(column.IsDbGenerated)));
            }

            if (column.IsVersionSpecified)
            {
                attributeList.Add(new CodeAttributeArgument("IsVersion", new CodePrimitiveExpression(column.IsVersion)));
            }

            if (column.UpdateCheckSpecified)
            {
                var updateCheck = string.Format("UpdateCheck.{0}", column.UpdateCheck.ToString());
                attributeList.Add(new CodeAttributeArgument("UpdateCheck", new CodeVariableReferenceExpression(updateCheck)));
            }

            return new CodeAttributeDeclaration("Column", attributeList.ToArray());
        }

        /// <summary>
        /// Gets the column attribute.
        /// </summary>
        /// <param name="association">The association.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <returns>the column attribute</returns>
        private static CodeAttributeDeclaration GetColumnAttribute(Association association, string fieldName)
        {
            var attributeList = new List<CodeAttributeArgument>();

            if (!string.IsNullOrEmpty(association.Name))
            {
                attributeList.Add(new CodeAttributeArgument("Name", new CodePrimitiveExpression(association.Name)));
            }

            if (!string.IsNullOrEmpty(fieldName))
            {
                attributeList.Add(new CodeAttributeArgument("Storage", new CodePrimitiveExpression(fieldName)));
            }

            if (!string.IsNullOrEmpty(association.ThisKey))
            {
                attributeList.Add(new CodeAttributeArgument("ThisKey", new CodePrimitiveExpression(association.ThisKey)));
            }

            if (!string.IsNullOrEmpty(association.OtherKey))
            {
                attributeList.Add(new CodeAttributeArgument("OtherKey", new CodePrimitiveExpression(association.OtherKey)));
            }

            if (association.CardinalitySpecified && association.Cardinality == Cardinality.One)
            {
                attributeList.Add(new CodeAttributeArgument("IsUnique", new CodePrimitiveExpression(true)));
            }

            if (association.IsForeignKeySpecified)
            {
                attributeList.Add(new CodeAttributeArgument("IsForeignKey", new CodePrimitiveExpression(association.IsForeignKey)));

                // Default delete rule for forign key relations
                if (association.IsForeignKey && string.IsNullOrEmpty(association.DeleteRule))
                {
                    association.DeleteRule = "CASCADE";
                }
            }

            if (!string.IsNullOrEmpty(association.DeleteRule))
            {
                attributeList.Add(new CodeAttributeArgument("DeleteRule", new CodePrimitiveExpression(association.DeleteRule)));
            }

            return new CodeAttributeDeclaration("Association", attributeList.ToArray());
        }
#else

        /// <summary>
        /// Creates the data context code compile unit.
        /// </summary>
        /// <param name="doc">The doc.</param>
        /// <returns>
        /// the code compile unit for the data context
        /// </returns>
        public static CodeTypeDeclaration CreateDataContextDeclaration(ref XmlDocument doc)
        {
            XmlElement root = doc.DocumentElement;

            if (root.Name != "Database")
            {
                throw new ArgumentException("Invalid root", root.Name);
            }

            // The class definition
            var className = root.Attributes.GetNamedItem("Class").Value;
            CodeTypeDeclaration classDefinition = new CodeTypeDeclaration(className)
                {
                    TypeAttributes = TypeAttributes.Public,
                    IsClass = true,
                    IsPartial = true
                };

            // Inherit from DataContext
            classDefinition.BaseTypes.Add("DataContext");

        #region Class Attributes

            //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "This is how we define LINQ tables.")]
            CodeAttributeDeclaration codeAttrDecl = new CodeAttributeDeclaration("System.Diagnostics.CodeAnalysis.SuppressMessage",
                new CodeAttributeArgument(new CodePrimitiveExpression("SA1401:FieldsMustBePrivate")),
                new CodeAttributeArgument(new CodePrimitiveExpression("Microsoft.StyleCop.CSharp.MaintainabilityRules")),
                new CodeAttributeArgument("Justification", new CodePrimitiveExpression("This is how we define LINQ tables.")));

            classDefinition.CustomAttributes.Add(codeAttrDecl);

        #endregion Class Attributes

        #region Constructor

            // Build the constructor
            var constructor = new CodeConstructor();
            constructor.Attributes = MemberAttributes.Public;

            // Declares a parameter of type string named "connectionString".
            constructor.Parameters.Add(new CodeParameterDeclarationExpression("System.String", "connectionString"));

            // Calls a base class constructor with the TestStringParameter parameter.
            constructor.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("connectionString"));

            // Add the constructor
            classDefinition.Members.Add(constructor);

        #endregion Constructor

        #region Add Table Definitions

            foreach (XmlNode node in root.ChildNodes)
            {
                if (node.Name == "Table")
                {
                    // public Table<LocationItem> Locations;
                    classDefinition.Members.Add(new CodeMemberField("Table`1[" + node["Type"].Attributes["Name"].Value + "]", node.Attributes["Member"].Value)
                    {
                        Attributes = MemberAttributes.Public
                    });
                }
            }

        #endregion Add Table Definitions

            return classDefinition;
        }

        /*
        <Type Name="Student" AccessModifier="Internal" Modifier="Abstract">
            <Column Name="FullName" Type="System.String" CanBeNull="false" />
            <Column Member="Id" Type="System.Int32" IsPrimaryKey="true" CanBeNull="false" />
            <Association Name="Student_ClassRoom" Member="ClassRooms" ThisKey="Id" OtherKey="Id" Type="ClassRoom" />
        </Type>
        */

        /// <summary>
        /// Creates the entity class for a given node definition.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="codeNamespace">The code namespace.</param>
        /// <returns>code type declaration of a class</returns>
        private static CodeTypeDeclaration CreateClassForNode(ref XmlNode typeNode)
        {
            if (typeNode == null || typeNode.Name != "Type")
            {
                throw new ArgumentException("Invalid Root", "Type");
            }

            // The class definition
            var className = typeNode.Attributes.GetNamedItem("Name").Value;
            CodeTypeDeclaration classDefinition = new CodeTypeDeclaration(className)
            {
                TypeAttributes = TypeAttributes.Public,
                IsClass = true,
                IsPartial = true
            };

            // Inherit from our custom LocalEntityBase (which is also generated!)
            classDefinition.BaseTypes.Add("LocalEntityBase");

            // Add Class Attributes
            // [Table]
            CodeAttributeDeclaration codeAttrDecl = new CodeAttributeDeclaration("Table");
            classDefinition.CustomAttributes.Add(codeAttrDecl);

            // Add a version column (http://msdn.microsoft.com/en-us/library/hh286406%28v=VS.92%29.aspx)
            var versionField = new CodeMemberField("Binary", "_version")
           {
               Attributes = MemberAttributes.Private
           };

            versionField.Comments.Add(new CodeCommentStatement("<summery>", true));
            versionField.Comments.Add(new CodeCommentStatement("Version column aids update performance.", true));
            versionField.Comments.Add(new CodeCommentStatement("</summery>", true));
            versionField.CustomAttributes.Add(new CodeAttributeDeclaration("Column", new CodeAttributeArgument("IsVersion", new CodePrimitiveExpression(true))));
            classDefinition.Members.Add(versionField);

            // Define the properties and associations
            foreach (XmlNode node in typeNode.ChildNodes)
            {
                if (node.Name == "Column")
                {
                    CodeMemberField theField;
                    CodeMemberProperty theProperty;

                    if (GetColumnDefinitions(node, out theField, out theProperty))
                    {
                        // Add the definition to the class
                        classDefinition.Members.Add(theField);
                        classDefinition.Members.Add(theProperty);
                    }
                }
                else if (node.Name == "Association")
                {
                    // TODO: Define the association
                }
            }

            return classDefinition;
        }

        /// <summary>
        /// Creates the entity code compile units.
        /// </summary>
        /// <param name="doc">The doc.</param>
        /// <param name="codeNamespace">The code namespace.</param>
        /// <returns></returns>
        public static IList<CodeTypeDeclaration> CreateEntityDeclarations(ref XmlDocument doc)
        {
            XmlElement root = doc.DocumentElement;

            if (root.Name != "Database")
            {
                throw new ArgumentException("Invalid Root", root.Name);
            }

            IList<CodeTypeDeclaration> entityDefinitions = new List<CodeTypeDeclaration>();
            CodeTypeDeclaration entityDefinition;
            foreach (XmlNode node in root.ChildNodes)
            {
                if (node.Name == "Table")
                {
                    var typeNode = node["Type"] as XmlNode;
                    if (typeNode != null)
                    {
                        entityDefinition = CreateClassForNode(ref typeNode);
                        if (entityDefinition != null)
                        {
                            entityDefinitions.Add(entityDefinition);
                        }
                    }
                }
            }

            return entityDefinitions;
        }

        /// <summary>
        /// Adds the column property.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="theField">The field.</param>
        /// <param name="theProperty">The property.</param>
        /// <returns>true if the transformation success; else false</returns>
        private static bool GetColumnDefinitions(XmlNode node, out CodeMemberField theField, out CodeMemberProperty theProperty)
        {
            // TODO: Handle errors (e.g. missing values)
            string type = node.Attributes["Type"].Value;
            string propName;

            if (node.Attributes["Member"] != null)
            {
                propName = node.Attributes["Member"].Value;
            }
            else if (node.Attributes["Name"] != null)
            {
                propName = node.Attributes["Name"].Value;
            }
            else
            {
                theField = null;
                theProperty = null;
                return false;
            }

            string fieldName = "_" + propName;

            // Define the private field
            theField = new CodeMemberField(type, fieldName)
            {
                Attributes = MemberAttributes.Private
            };

            //Adding a doc comment
            theField.Comments.Add(new CodeCommentStatement("<summery>", true));
            theField.Comments.Add(new CodeCommentStatement(string.Format("The storage field for {0}", propName), true));
            theField.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Define the property
            theProperty = new CodeMemberProperty
            {
                Attributes = MemberAttributes.Public,
                Name = propName,
                HasGet = true,
                Type = new CodeTypeReference(type)
            };

            // Add an attribute ([Column])
            theProperty.CustomAttributes.Add(new CodeAttributeDeclaration("Column"));

            //Adding a doc comment
            theProperty.Comments.Add(new CodeCommentStatement("<summery>", true));
            theProperty.Comments.Add(new CodeCommentStatement(string.Format("Gets or sets the {0}", propName), true));
            theProperty.Comments.Add(new CodeCommentStatement("</summery>", true));

            // Define the getter
            theProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));

            // Define the setter

            // Notify the  PropertyChanging event, passing property name
            CodeMethodInvokeExpression propChanging = new CodeMethodInvokeExpression(
                new CodeBaseReferenceExpression(),                                          // targetObject that contains the method to invoke.
                "NotifyPropertyChanging",                                                   // methodName indicates the method to invoke.
                new CodeExpression[] { new CodeVariableReferenceExpression(string.Format("\"{0}\"", propName)) }     // parameters array contains the parameters for the method.
                );

            // Notify the  PropertyChanged event, passing property name
            CodeMethodInvokeExpression propChanged = new CodeMethodInvokeExpression(
                new CodeBaseReferenceExpression(),                                          // targetObject that contains the method to invoke.
                "NotifyPropertyChanged",                                                    // methodName indicates the method to invoke.
                new CodeExpression[] { new CodeVariableReferenceExpression(string.Format("\"{0}\"", propName)) }     // parameters array contains the parameters for the method.
                );

            CodeConditionStatement conditionalStatement = new CodeConditionStatement(
                            new CodeBinaryOperatorExpression(
                                new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                                CodeBinaryOperatorType.IdentityInequality,
                                new CodePropertySetValueReferenceExpression()),
                // True statements
                new CodeStatement[]{
                                new CodeExpressionStatement(propChanging),
                                // Assign value
                                new CodeAssignStatement( new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()),
                                new CodeExpressionStatement(propChanged)
                });

            theProperty.SetStatements.Add(conditionalStatement);

            return true;
        }

#endif
    }
}